/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include "Bruinbase.h"
#include "SqlEngine.h"
#include "BTreeIndex.h"
#include <limits>

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
  RecordFile  rf;      // RecordFile containing the table
  RecordId    rid;     // record cursor for table scanning
  BTreeIndex  ind;     // index file for table
  IndexCursor cursor;  // index cursor for index scanning

  RC     rc;
  int    key;     
  string value;
  int    count;
  int    diff;
  unsigned i, j, m;
  int    leftBound, rightBound;	// For expressing the range of a query
  bool checksKey = false;	// For checking if index is worth using
  bool checksValue = false;	// For checking if page reads can be avoided
  bool checksRange = false;	// For checking if a range exists
  bool checksNotEqual = false;	// For checking if query checks for inequality
  
  rid.pid = rid.sid = 0;
  count = 0;
		
  // open the table file
  if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
    fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
    return rc;
  }
  
  // Open the table's index file, if it exists and use it on query; if not, query without index
  if ((rc = ind.open(table + ".idx", 'r')) == 0)
    goto query_with_index;
  
  // scan the table file from the beginning
 query_without_index:
  while (rid < rf.endRid()) {
    // read the tuple
    if ((rc = rf.read(rid, key, value)) < 0) {
      fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
      goto exit_select;
    }
	    
    // check the conditions on the tuple
    for (i = 0; i < cond.size(); i++) {
      // compute the difference between the tuple value and the condition value
      switch (cond[i].attr) {
      case 1:
        diff = key - atoi(cond[i].value);
        break;
      case 2:
        diff = strcmp(value.c_str(), cond[i].value);
				break;
      }

      // skip the tuple if any condition is not met
      switch (cond[i].comp) {
      case SelCond::EQ:
				if (diff != 0) goto next_tuple;
				break;
      case SelCond::NE:
				if (diff == 0) goto next_tuple;
				break;
      case SelCond::GT:
				if (diff <= 0) goto next_tuple;
				break;
      case SelCond::LT:
				if (diff >= 0) goto next_tuple;
				break;
      case SelCond::GE:
				if (diff < 0) goto next_tuple;
				break;
      case SelCond::LE:
				if (diff > 0) goto next_tuple;
				break;
      }
    }
      
    // the condition is met for the tuple. 
    // increase matching tuple counter
    count++;

    // print the tuple 
    switch (attr) {
    case 1:  // SELECT key
      fprintf(stdout, "%d\n", key);
      break; 
    case 2:  // SELECT value
      fprintf(stdout, "%s\n", value.c_str());
      break;
    case 3:  // SELECT *
      fprintf(stdout, "%d '%s'\n", key, value.c_str());
      break;
    }
	
    // move to the next tuple
  next_tuple:
    ++rid;
  } 
	
  // print matching tuple count if "select count(*)"
 print_count:
  if (attr == 4) {
    fprintf(stdout, "%d\n", count);
  }
  rc = 0;
  
  goto exit_select;
	
  // Otherwise, query using the index
 query_with_index:
  // Set up bounds to limits of int
  leftBound = numeric_limits<int>::min();
  rightBound = numeric_limits<int>::max();
  
  // Set up range for query using key conditions
  for (j = 0; j < cond.size(); j++)
    { 
      if(cond[j].attr == 1)
			{  
				switch(cond[j].comp)
				{
				case SelCond::EQ:
					// If value is within current range, change range
					if(atoi(cond[j].value) >= leftBound && atoi(cond[j].value) <= rightBound)
					{
						leftBound = rightBound = atoi(cond[j].value);
						checksRange = true;
					}
					
					// Otherwise, query is false, so exit
					else
					{
						ind.close();
						goto print_count;
					}
					break;
				case SelCond::NE:
					checksNotEqual = true;	// Only query without index if there is inequality checking with no range
					break;
				case SelCond::LT:
					if(atoi(cond[j].value) - 1 < rightBound)
					{
						rightBound = atoi(cond[j].value) - 1;
						checksRange = true;
					}
					break;
				case SelCond::GT:
					if(atoi(cond[j].value) + 1 > leftBound)
					{
						leftBound = atoi(cond[j].value) + 1;
						checksRange = true;
					}
					break;
				case SelCond::LE:
					if(atoi(cond[j].value) < rightBound)
					{
						rightBound = atoi(cond[j].value);
						checksRange = true;
					}
					break;
				case SelCond::GE:
					if(atoi(cond[j].value) > leftBound)
					{
						leftBound = atoi(cond[j].value);
						checksRange = true;
					}
					break;
				}
	  
				checksKey = true;
			}
	
      // If any condition involves value, set flag to make sure pages are read
      else  
			    checksValue = true;
    }

  // If query doesn't use key or doesn't have a range and checks inequality, don't bother using index
  // UNLESS it is count(*) only
  if((!checksKey || (!checksRange && checksNotEqual)) && !(attr == 4 && cond.size() == 0)) 
     goto query_without_index; 

  // Set cursor to left bound of range; if left bound not specified, scan entire table
  if((rc = ind.locate(leftBound, cursor)) < 0)
    {
      fprintf(stderr, "Error: while locating a tuple from table %s\n", table.c_str());
      goto exit_select;
    }
	
  // Read all tuples within range
  while(!(rc = ind.readForward(cursor, key, rid)) && key <= rightBound) 
    {
      if ((attr == 1 && !checksValue) || (attr == 4 && cond.size() == 0))
				goto skip_read;
				
      // read the tuple
      if ((rc = rf.read(rid, key, value)) < 0) {
				fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
				ind.close();
				goto exit_select;
      }

      // check the conditions on the tuple for values only since key range is already specified
      for (m = 0; m < cond.size(); m++) {
				// Only check key conditions if comparator is NE
				if(cond[m].attr == 1 && cond[m].comp == SelCond::NE)
					diff = key - atoi(cond[m].value);
				
				// compute the difference between the tuple value and the condition value
				else if(cond[m].attr == 2)
					diff = strcmp(value.c_str(), cond[m].value);

				// If no inequality checking on key or condition checking on value, skip the comparison
				else
					goto skip_comparison;
					
				// skip the tuple if any condition is not met
				switch (cond[m].comp) {
				case SelCond::EQ:
					if (diff != 0) goto next_ind_tuple;
					break;
				case SelCond::NE:
					if (diff == 0) goto next_ind_tuple;
					break;
				case SelCond::GT:
					if (diff <= 0) goto next_ind_tuple;
					break;
				case SelCond::LT:
					if (diff >= 0) goto next_ind_tuple;
					break;
				case SelCond::GE:
					if (diff < 0) goto next_ind_tuple;
					break;
				case SelCond::LE:
					if (diff > 0) goto next_ind_tuple;
					break;
				skip_comparison: /* do nothing*/ ;
				}
      }
      // the condition is met for the tuple. 
      // increase matching tuple counter
    skip_read:
      count++;

      // print the tuple 
      switch (attr) {
      case 1:  // SELECT key
				fprintf(stdout, "%d\n", key);
				break;
      case 2:  // SELECT value
				fprintf(stdout, "%s\n", value.c_str());
				break;
      case 3:  // SELECT *
				fprintf(stdout, "%d '%s'\n", key, value.c_str());
				break;
      }

      // move to the next tuple
    next_ind_tuple: /* do nothing */ ;
    }
  
  // Close the index file
  ind.close();
  goto print_count;

  // close the table file and return
 exit_select:
  rf.close();
  return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
  int key;
  char buf[1024];
  fstream loadFile;
  string line, value;
  RecordId rid;
  BTreeIndex idx;
  RC rc;
	
  // Open loadfile
  loadFile.open(loadfile.c_str(), fstream::in);

  // If loadfile opened successfully, process data
  if(loadFile.is_open())
    {
      // Create a new table
      string tableName = table + ".tbl";
      RecordFile tableFile(tableName, 'w');
		
      // If index creation is desired, create a new index
      if(index)
			{
				// Form index name
				string indexname(table);
				indexname += ".idx";
			
				// Create index page file
				if((rc = idx.open(indexname, 'w')) < 0)
					return rc;
			}
		
      // Parse all lines in load file
      while(!loadFile.eof())
			{
				// Get each line
				loadFile.getline(buf, 1024);
			
				// Make sure not to parse empty line from EOF
				if(!loadFile.eof() && !loadFile.fail())
					{
						// Put line into a string for parsing
						line = string(buf, strlen(buf));
						
						// Parse string
						parseLoadLine(line, key, value);
						
						// Append to table; if error, clean up
						if(tableFile.append(key, value, rid) != 0)
						{					
							loadFile.close();
							tableFile.close();
					
							if(index)
								idx.close();
									
							return RC_FILE_WRITE_FAILED;
						}
				
						// If index desired, insert into index
						if(index)
						{
							if((rc = idx.insert(key, rid)) < 0)
								{						
									loadFile.close();
									tableFile.close();
									idx.close();
										
									return rc;
								}
						}
					}
			
				else if(!loadFile.eof() && loadFile.fail())
					{				
						loadFile.close();				
						tableFile.close();
						
						if(index)
				idx.close();
						
						return RC_FILE_READ_FAILED;
					}
			}
		
      // Close table file when finished reading from loadfile
      tableFile.close();
		
      // Close index if it was created
      if(index)
				idx.close();
		}
	
  // Otherwise, flag an error and clean up
  else
    return RC_FILE_OPEN_FAILED;
	
  // Close loadfile
  loadFile.close();
	
  return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
  const char *s;
  char        c;
  string::size_type loc;
    
  // ignore beginning white spaces
  c = *(s = line.c_str());
  while (c == ' ' || c == '\t') { c = *++s; }

  // get the integer key value
  key = atoi(s);

  // look for comma
  s = strchr(s, ',');
  if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

  // ignore white spaces
  do { c = *++s; } while (c == ' ' || c == '\t');
    
  // if there is nothing left, set the value to empty string
  if (c == 0) { 
    value.erase();
    return 0;
  }

  // is the value field delimited by ' or "?
  if (c == '\'' || c == '"') {
    s++;
  } else {
    c = '\n';
  }

  // get the value string
  value.assign(s);
  loc = value.find(c, 0);
  if (loc != string::npos) { value.erase(loc); }

  return 0;
}
